﻿using System.Collections;
using System.Diagnostics;
using System.Text;
using DimensionsHelper.Common.Utils;
using DimensionsHelper.LanguageService.Protocol;

namespace DimensionsHelper.LanguageService.Syntax;

[DebuggerDisplay("{ToDisplayString(),nq}")]
public class SourceNodeList : ITextRange, IReadOnlyList<SourceNode>
{
    private int _count;
    private bool _dirty;
    private SourceNode[] _elements = new SourceNode[8];
    private string _text = string.Empty;


    public bool IsReadOnly => false;


    public IEnumerator<SourceNode> GetEnumerator()
    {
        return new Enumerator(this);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }


    public int Count
    {
        get => _count;

        private set
        {
            _count = value;
            _dirty = true;
        }
    }


    public SourceNode this[int index]
    {
        get
        {
            if (unchecked(index < 0 || (uint)index >= (uint)Count))
            {
                throw new IndexOutOfRangeException(nameof(index));
            }

            return _elements[index];
        }

        set
        {
            if (unchecked(index < 0 || (uint)index >= (uint)Count))
            {
                throw new IndexOutOfRangeException(nameof(index));
            }

            _elements[index] = value;
        }
    }


    public TextSpan Span => TextSpan.Create(Uri, Start, End);


    public int Start => Count > 0 ? _elements[0].Start : 0;


    public int StartLine => Count > 0 ? _elements[0].StartLine : 0;


    public int End => Count > 0 ? _elements[Count - 1].End : 0;


    public int EndLine => Count > 0 ? _elements[Count - 1].EndLine : 0;


    public int Width => Count > 0 ? End - Start : 0;


    public string Text => ToString();


    public bool IsEmpty => Width == 0;


    public bool IsComment => false;


    public void Offset(int offset)
    {
        _elements.ForEach(n => n.Offset(offset));
    }


    public DocumentUri Uri { get; internal set; } = DocumentUri.Empty;


    private int ComputeCapacity(int insertCount)
    {
        int capacity = Count == 0 ? 8 : _elements.Length;

        while (capacity < Count + insertCount)
        {
            capacity *= 2;
        }

        return capacity;
    }


    private void Grow(int emptyIndex, int count = 1)
    {
        Debug.Assert(emptyIndex >= 0 && emptyIndex <= Count);
        Debug.Assert(count >= 1);

        SourceNode[] array = new SourceNode[ComputeCapacity(count)];

        if (emptyIndex == Count)
        {
            Array.Copy(_elements, array, Count);
        }
        else if (emptyIndex == 0)
        {
            Array.Copy(_elements, 0, array, count, Count);
        }
        else
        {
            Array.Copy(_elements, 0, array, 0, emptyIndex);
            Array.Copy(_elements, emptyIndex, array, emptyIndex + count, Count - emptyIndex);
        }

        _elements = array;
    }


    private void EnsurePlace(int index, int count = 1)
    {
        Debug.Assert(index >= 0);

        if (Count + count >= _elements.Length)
        {
            Grow(index, count);
        }
        else
        {
            int i = Count + count - 1;
            int j = Count - 1;
            while (i >= index + count)
            {
                _elements[i] = _elements[j];
                --i;
                --j;
            }
        }
    }


    private void InsertAt(SourceNode token, int index)
    {
        Debug.Assert(index >= 0);
        EnsurePlace(index);
        _elements[index] = token;
        ++Count;
    }


    private void InsertRange(SourceNodeList list, int index)
    {
        Debug.Assert(index >= 0);
        EnsurePlace(index, list.Count);
        Array.Copy(list._elements, 0, _elements, index, list.Count);
    }


    public SourceNode? LastOrDefault()
    {
        return Count > 0 ? _elements[Count - 1] : null;
    }


    public SourceNode Last()
    {
        return _elements[Count - 1];
    }


    public SourceNode? FirstOrDefault()
    {
        return Count > 0 ? _elements[0] : null;
    }


    public SourceNode First()
    {
        return _elements[0];
    }


    private int IndexOf(ITextRange textNode)
    {
        int index = CollectionHelper.BinarySearch(
            _elements,
            0,
            Count,
            textNode.Span,
            (token, span) => token.Span.Start - span.Start);

        if (index < 0)
        {
            index = ~index - 1;
        }

        return index;
    }


    public void Add(SourceNode value)
    {
        if (Count == 0)
        {
            _elements[0] = value;
            ++Count;
            return;
        }

        if (value.Span < _elements[0].Span)
        {
            InsertAt(value, 0);
        }
        else if (value.Span > _elements[Count - 1].Span)
        {
            InsertAt(value, Count);
        }
        else
        {
            InsertAt(value, IndexOf(value));
        }
    }


    public void AddRange<T>(IEnumerable<T> list) where T : SourceNode
    {
        foreach (T item in list)
        {
            Add(item);
        }
    }


    public void AddRange(SourceNodeList list)
    {
        if (list.Count == 0)
        {
            return;
        }


        if (Count == 0)
        {
            _elements = new SourceNode[list._elements.Length];
            Array.Copy(list._elements, _elements, list.Count);
            Count = list.Count;
            return;
        }

        int listPos = list.Start;
        int listEnd = list.End;

        if (listPos < _elements[0].Start)
        {
            InsertRange(list, 0);
        }
        else if (listEnd > _elements[Count - 1].Span.End)
        {
            InsertRange(list, Count);
        }
        else
        {
            InsertRange(list, IndexOf(list));
        }
    }


    private bool RemoveAt(int index)
    {
        if (index < 0 || index >= Count)
        {
            return false;
        }

        for (int i = index; i < Count - 1; i++)
        {
            _elements[i] = _elements[i + 1];
        }

        Count--;
        return true;
    }


    public bool Remove(SourceNode item)
    {
        int index = Array.IndexOf(_elements, item);
        return RemoveAt(index);
    }


    public string ToDisplayString()
    {
        return $"Count = {Count}";
    }


    public override string ToString()
    {
        if (Count == 0)
        {
            return string.Empty;
        }

        if (!_dirty)
        {
            return _text;
        }

        StringBuilder builder = new();
        SourceNode? last = null;
        for (int i = 0; i < Count; i++)
        {
            SourceNode node = _elements[i];

            if (last == null)
            {
                last = node;
            }
            else if (node.Start - last.End > 1)
            {
                builder.Append(' ');
            }

            builder.Append(node);
        }

        return _text = builder.ToString();
    }


    private struct Enumerator(SourceNodeList list) : IEnumerator<SourceNode>
    {
        private int _index = -1;

        public readonly SourceNode Current => list[_index];

        readonly object IEnumerator.Current => list[_index];

        public readonly void Dispose()
        {
        }


        public bool MoveNext()
        {
            _index++;
            return _index < list.Count;
        }


        public void Reset()
        {
            _index = -1;
        }
    }
}